#pragma once

#include <list>
#include "cache.h"
#include "mswin/shared_resource.h"
#include "mswin/event.h"
#include "mswin/dir_lock.h"

namespace icache {

class disc_cache : public cache {
public:
	disc_cache();
	//if no item_id_deserializer is specified than directory is cleaned from all images
	disc_cache(const string & root_path, disc_size_type quota, item_id_deserializer * d = 0);
	virtual ~disc_cache();
	void reinit(const string & root_path, disc_size_type quota, item_id_deserializer * d = 0);
	void quota(disc_size_type q);
	bool is_open() const;
	void close();
public:
	disc_size_type quota() const;
	disc_size_type size() const; ///< size on disc that cache currently occupies
	int items(); ///< the number of images currently in the cache
	string const & root_path() const;
	bool empty() const;
public: //cache interface
	virtual void put(cached_image_sptr const & c, last_used_position p = most_recently_used);
	virtual cached_image_sptr get(item_id_sptr const & id, last_used_position p = most_recently_used);
	virtual cached_image_sptr try_get(item_id_sptr const & id, last_used_position p = most_recently_used);
	virtual bool touch(item_id_sptr const & id, last_used_position p = most_recently_used);
	virtual void remove(item_id_sptr const & id);
	virtual void purge();
public:
	bool erase_contents_on_exit;
private:
	struct element {
		element() { }
		element(item_id_sptr const & i) : id(i), size(0) { }
	public:
		item_id_sptr id;
		string rel_path; ///< of the image, description differs with extension only
		size_t size;
		string desc_rel_path() const;
	};
	struct element_n_time;
	friend struct equal_item_id;
	typedef std::list<element> lru_type; ///< least recently used are in front of list, most recently used are at back
	typedef std::list<item_id_sptr> items_in_use_type;
private: //following functions should be called only than resources are claimed (locked)
	void clear();
	void enforce_quota();
	void erase_element_on_disc(element const & e);
	void write_element_on_disc(element & e, byte_image const & im);
	void read_element_from_disc(element const & e, byte_image & im);
	void reposition_element(lru_type::iterator i, last_used_position p);
private:
	enum claim_if_not_found {
		do_nothing, ///< do not block an item (and do not update items-in-use list)
		create_new, ///< new item is placed in lru list as well as in items-in-use list
		calim_but_not_create ///< updates items-in-use list but never creates new lru element
	};
	class claim_item {
	public:
		claim_item(disc_cache & dc, item_id_sptr const & id, claim_if_not_found policy);
		~claim_item();
		bool claimed() const;
		element & e() const;
	public:
		int size_change;
		last_used_position lru_pos;
	private:
		disc_cache & c;
		bool _claimed;
		lru_type::iterator i;
		items_in_use_type::iterator j;
	};
private:
	mswin::shared_rw_resource _entire_directory; ///<'w' for total clean up or change of quota, 'r' for single file access
	string _root_path;
	mswin::dir_lock _dir_lock;
	disc_size_type _quota;
private:
	mswin::shared_resource _files; ///< controls access to lru-files and items-in-use lists as well as _size variable
	disc_size_type _size; ///< total size of elements in lru
	lru_type lru;
	items_in_use_type items_in_use;
private:
	mswin::event _file_release; ///< event pulses each time file is released
};

//inlines

inline bool disc_cache::is_open() const
	{ return _dir_lock.is_locked(); }

inline disc_size_type disc_cache::quota() const
	{ return is_open() ? _quota : 0; }

inline disc_size_type disc_cache::size() const
	{ return _size; }

inline string const & disc_cache::root_path() const
	{ return _root_path; }

inline bool disc_cache::empty() const
	{ return _size == 0; }

inline bool disc_cache::claim_item::claimed() const
	{ return _claimed; }

inline disc_cache::element & disc_cache::claim_item::e() const
	{ return *i; }

} //namespace icache
